package com.qingyun.system.service.impl.oss;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.qingyun.system.common.properties.AliYunOssProperties;
import com.qingyun.system.model.dto.FileInfo;
import com.qingyun.system.model.dto.TreeFileList;
import com.qingyun.system.model.dto.TreeListChildren;
import com.qingyun.system.service.OssService;
import com.qingyun.system.service.SysUserService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * （阿里云OSS对象存储）文件上传服务类
 */

@Service
@RequiredArgsConstructor
public class AliyunOssServiceImpl implements OssService {

    private final SysUserService sysUserService;

    private final AliYunOssProperties aliYunOssProperties;

    @Resource
    private OSS ossClient;


    /**
     * 文件上传
     *
     * @param file 表单文件对象
     * @return FileInfo
     */
    @Override
    public FileInfo uploadFile(MultipartFile file) {

        return uploadToOss(file);
    }

    /**
     * 文件上传到 Minio 服务器
     *
     * @param file 文件对象
     * @return 文件上传信息
     */
    private FileInfo uploadToOss(MultipartFile file) {
        // 生成文件名(日期文件夹)
        String originalFilename = file.getOriginalFilename();
        if (StrUtil.isBlank(originalFilename)) {
            throw new RuntimeException("文件名不能为空");
        }
        String extName = FileNameUtil.extName(originalFilename); // 获取文件扩展名（不包含.）
        //为了避免上传的文件重名,重新生成一个新的文件名
        originalFilename = IdUtil.fastSimpleUUID()+"."+extName;
        // 文件名相同会进行覆盖
        String fileName = DateUtil.format(LocalDateTime.now(), "yyyyMMdd") + "/" + originalFilename;
        //  try-with-resource 语法糖自动释放流
        try (InputStream inputStream = file.getInputStream()) {
            // 上传文件到阿里云OSS对象存储里面
            ossClient.putObject(
                    aliYunOssProperties.getBucketName(),
                    fileName,
                    inputStream);
            // 返回文件路径
            //上传完成后   :文件的访问路径为 https://桶名.地域节点/yyyyMMdd/UUID.jpeg
            String fileUrl = aliYunOssProperties.getCustomDomain() + "/" + fileName;;
            FileInfo fileInfo = new FileInfo();
            fileInfo.setName(fileName);
            fileInfo.setUrl(fileUrl);
            return fileInfo;
        } catch (Exception e) {
            throw new RuntimeException("文件上传失败");
        }
    }

    /**
     * 头像上传
     *
     * @param file 表单文件对象
     * @return FileInfo
     */
    @Override
    public FileInfo uploadAvatar(MultipartFile file) {
        FileInfo fileInfo = uploadToOss(file);
        // 更新数据库中当前用户的头像存储地址
        boolean b = sysUserService.updateAvatarUrl(fileInfo.getUrl());

        if (b) {
            return fileInfo;
        } else {
            throw new RuntimeException("头像上传失败");
        }
    }

    /**
     * 文件下载
     * @param objectName 文件名
     * @return 下载文件对象信息
     */
    @Override
    public ResponseEntity<InputStreamResource> downLoadFile(String objectName) {
        try {
            // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
            OSSObject ossObject = ossClient.getObject(aliYunOssProperties.getBucketName(), objectName);
            // 读取文件内容。
            InputStream inputStream = ossObject.getObjectContent();
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputStream.readAllBytes());
            // 设置响应头触发浏览器下载
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            // objectName = yyyyMMdd/UUID
            headers.setContentDispositionFormData("attachment", objectName);
            return ResponseEntity.ok().headers(headers).body(new InputStreamResource(byteArrayInputStream));
        } catch (Exception e) {
            throw new RuntimeException("文件下载失败",e);
        }
    }

    /**
     * 批量获取文件 url
     * @return 文件 url 列表
     */
    @Override
    public List<TreeFileList> getFileList() {
        List<FileInfo> fileUrlList = new ArrayList<>();
        try {
            // 列出桶中所有对象
            ObjectListing objectListing = ossClient.listObjects(aliYunOssProperties.getBucketName());
            List<OSSObjectSummary> objectSummaries = objectListing.getObjectSummaries();
            for (OSSObjectSummary objectSummary : objectSummaries) {
                // 取得桶下的所有文件名
                String fileName = objectSummary.getKey();
                String fileUrl = getFileUrl(fileName);
                // 设置文件名以及文件 url
                FileInfo fileInfo = new FileInfo();
                fileInfo.setName(fileName);
                fileInfo.setUrl(fileUrl);
                fileUrlList.add(fileInfo);
            }

            // 关闭OSS客户端
            ///ossClient.shutdown();

        } catch (Exception e) {
            throw new RuntimeException("获取文件列表失败", e);
        }

        return buildFileList(fileUrlList);
    }

    /**
     * 删除单个文件
     * @param fileName 文件名
     */
    @Override
    public void deleteFile(String fileName) throws Exception {
        try {
            ossClient.deleteObject(aliYunOssProperties.getBucketName(), fileName);
        } catch (Exception e) {
            throw new Exception("删除文件失败",e);
        }
    }

    /**
     * 删除多个文件
     * @param fileNameList 文件名列表
     */
    @Override
    public void deleteFileBatch(List<String> fileNameList) {
        ossClient.deleteObjects(
                new DeleteObjectsRequest(aliYunOssProperties.getBucketName())
                        .withKeys(fileNameList));
    }

    /**
     * 批量文件上传
     * @param files 文件列表
     * @return 上传后的文件信息集合
     */
    @Override
    public List<FileInfo> uploadFiles(List<MultipartFile> files) {
        List<FileInfo> fileInfoList = new ArrayList<>();
        for (MultipartFile file : files) {
            FileInfo fileInfo = uploadToOss(file);
            fileInfoList.add(fileInfo);
        }
        return fileInfoList;
    }

    /**
     * 文件批量下载
     * @param fileNameList 文件名列表
     * @return zip 文件
     */
    @Override
    public ResponseEntity<byte[]> downLoadFileZip(List<String> fileNameList) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ZipOutputStream zos = new ZipOutputStream(baos)) {

            // 遍历文件名列表，从阿里云OSS对象存储获取文件并且添加到压缩流文件中
            for (String fileName : fileNameList) {
                // 创建获取对象请求
                GetObjectRequest getObjectRequest = new GetObjectRequest(aliYunOssProperties.getBucketName(), fileName);

                InputStream inputStream = ossClient.getObject(getObjectRequest).getObjectContent();
                // 写入 zip 文件
                ZipEntry zipEntry = new ZipEntry(fileName);
                zos.putNextEntry(zipEntry);
                IOUtils.copy(inputStream, zos);
                zos.closeEntry();

                // 关闭输入流
                inputStream.close();
            }

            // 关闭OSS客户端
            //ossClient.shutdown();

            // 设置响应头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", "file.zip");

            // 返回 zip 文件
            return ResponseEntity.ok()
                    .headers(headers)
                    .body(baos.toByteArray());
        } catch (IOException e) {
            throw new RuntimeException("文件下载失败", e);
        }
    }

    /**
     * 查询OSS文件系统中文件的 url
     * @param fileName 文件名
     * @return 文件 url
     */
    private String getFileUrl(String fileName) throws Exception {
        String newFileUrl;
        try {
            // 设置生成预签名URL的请求
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(aliYunOssProperties.getBucketName(), fileName);
            // 设置过期时间，这里示例设置为1小时后过期，可根据实际需求调整
            Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
            request.setExpiration(expiration);
            // 获取预签名URL
            URL signedUrl = ossClient.generatePresignedUrl(request);
            newFileUrl = signedUrl.toString();
            // 去除URL中的查询参数部分（如果有需求去除的话，和Minio代码逻辑中去除部分对应）
            newFileUrl = newFileUrl.substring(0, newFileUrl.indexOf("?"));
            // 关闭OSS客户端
            //ossClient.shutdown();
        } catch (Exception e) {
            throw new RuntimeException("获取文件 url 失败", e);
        }
        return newFileUrl;
    }

    /**
     * 将 Minio 文件列表转换为前端需要的树形格式。
     *
     * @param minioFileList Minio 文件列表
     * @return 格式化之后的数据
     */
    private List<TreeFileList> buildFileList(List<FileInfo> minioFileList) {
        List<TreeFileList> fileList = new ArrayList<>();
        Map<String, List<TreeListChildren>> folderMap = new HashMap<>();

        for (FileInfo file : minioFileList) {
            TreeFileList fileList1 = new TreeFileList();
            int isDirIndex = file.getName().indexOf("/");
            if (isDirIndex == -1) {
                // 文件情况下
                fileList1.setId(UUID.randomUUID().toString());
                fileList1.setName(file.getName());
                fileList1.setUrl(file.getUrl());
                fileList1.setDir(false);
                fileList1.setFullPath(file.getName());
                fileList.add(fileList1);
            } else {
                // 文件夹情况下
                String folderName = file.getName().substring(0, isDirIndex);
                if (!folderMap.containsKey(folderName)) {
                    folderMap.put(folderName, new ArrayList<>());
                    fileList1.setId(UUID.randomUUID().toString());
                    fileList1.setName(folderName);
                    fileList1.setUrl("");
                    fileList1.setDir(true);
                    fileList1.setChildren(new ArrayList<>());
                    fileList.add(fileList1);
                }

                // 添加到对应的文件夹children中
                String fileName = file.getName().substring(isDirIndex + 1);
                TreeListChildren childNode = new TreeListChildren();
                childNode.setName(fileName);
                childNode.setUrl(file.getUrl());
                childNode.setFullPath(file.getName());
                childNode.setParentPath(folderName);
                folderMap.get(folderName).add(childNode);
            }
        }

        // 递归处理文件夹的 children
        for (TreeFileList file : fileList) {
            if (file.isDir()) {
                file.setChildren(folderMap.get(file.getName()));
            }
        }
        return fileList;
    }
}
